package io.sundial.protocol.impl;

import io.sundial.conversion.Converter;
import io.sundial.protocol.exception.MarshallingException;
import io.sundial.protocol.exception.UnmarshallingException;
import io.sundial.util.ResKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Map;

/**
 * Sundial 协议
 *
 * @author Payne 646742615@qq.com
 * 2018/12/24 16:54
 */
public class SundialProtocol extends BasicProtocol {
    private final Logger logger = LoggerFactory.getLogger(this.getName());

    @Override
    public String getName() {
        return "sundial";
    }

    @Override
    public String getVersion() {
        return "1.0";
    }

    @Override
    public byte[] marshall(Object obj) throws MarshallingException {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            String name = getName();
            String version = getVersion();

            // region sundial/1.0
            bos.write((name + "/" + version).getBytes());
            bos.write(CRLF);
            // endregion
            // region Content-Type: CONTENT-TYPE
            String contentType = getContentType();
            bos.write(("Content-Type: " + contentType).getBytes());
            bos.write(CRLF);
            // endregion
            // region Content-Length: CONTENT-LENGTH
            byte[] body = serialize(obj);
            int contentLength = body.length;
            bos.write(("Content-Length: " + contentLength).getBytes());
            bos.write(CRLF);
            // endregion

            // region BODY
            bos.write(CRLF);
            bos.write(body);
            // endregion

            bos.flush();
            return bos.toByteArray();
        } catch (Exception e) {
            throw new MarshallingException(e);
        }
    }

    @Override
    public <T> T unmarshal(byte[] data, Class<T> type) throws UnmarshallingException {
        try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
            String contentType = null;
            int contentLength = -1;
            String line;
            while (!(line = ResKit.readln(bis)).isEmpty()) {
                int idx = line.indexOf(":");
                if (idx < 0) {
                    continue;
                }
                String key = line.substring(0, idx).trim();
                String value = line.substring(idx + 1).trim();
                switch (key.toUpperCase()) {
                    case "CONTENT-TYPE":
                        contentType = value;
                        break;
                    case "CONTENT-LENGTH":
                        contentLength = Integer.valueOf(value);
                        break;
                }
            }

            byte[] body = new byte[contentLength < 0 ? bis.available() : contentLength];
            int read = bis.read(body);
            if (read != contentLength) {
                logger.warn("expecting Content-Length: {} but got: {}", contentLength, read);
            }

            Map<String, Converter> supportConverters = getSupportConverters();
            Converter converter = contentType == null ? null : supportConverters.get(contentType.toUpperCase());
            return converter != null ? converter.deserialize(body, type) : this.deserialize(body, type);
        } catch (Exception e) {
            throw new UnmarshallingException(e);
        }
    }
}
